
/**
* Class that handles Date and Time related operations.
* It is very similar to .NET's DateTime class
* Find reference and documentation at: http://menendezpoo.com
*/

//Constructor
function DateTime(){
    var year = 0;
    var month = 0;
    var day = 0;
    var hour = 0;
    var minute = 0;
    var second = 0;
    var millisecond = 0;
    
    switch(arguments.length){
        case 0:
            var d = new Date();
            year = d.getFullYear();
            month = d.getMonth() + 1;
            day = d.getDay();
            hour = d.getHours();
            minute = d.getMinutes();
            second = d.getSeconds();
            millisecond = d.getMilliseconds();
            break;
        case 1:
            millisecond = arguments[0];
            break;
        case 3:
            year = arguments[0];
            month = arguments[1];
            day = arguments[2];
            break;
        case 6:
            year = arguments[0];
            month = arguments[1];
            day = arguments[2];
            hour = arguments[3];
            minute = arguments[4];
            second = arguments[5];
            break;
        case 7:
            year = arguments[0];
            month = arguments[1];
            day = arguments[2];
            hour = arguments[3];
            minute = arguments[4];
            second = arguments[5];
            millisecond = arguments[6];
            break;
        default:
            throw("No constructor supports " + arguments.length + " arguments");
    }
    
    if(!year && !month && !day)
        days = 0;
    else
        days = Math.round(this.absoluteDays(year, month, day));
    
    this.span = new TimeSpan(days, hour, minute, second, millisecond);
    
};

DateTime.prototype = {
    
        toString : function(){
            return this.year() + "/" + TimeSpan.pad(this.month()) + "/" + TimeSpan.pad(this.day()) +  " " + this.timeOfDay();
        },
                
        /* Methods */
        absoluteDays : function(year, month, day){
            
            function div(a,b){ return Math.round(a/b); }
            var num = 0;
            var num2= 1;
            var numArray = !DateTime.isLeapYear(year) ? DateTime.monthDays : DateTime.monthDaysLeapYear;
            while(num2 < month){
                num += numArray[num2++];
            }
            return ((((((day - 1) + num) + (365 * (year - 1))) + ((year -1 ) / 4)) - (( year - 1) / 100)) + ((year - 1) / 400));s
        },
        
        add : function(timespan){
            return new DateTime(this.span._millis + timespan._millis);
        },
        
        addDays : function(days){
            return new DateTime(this.span._millis + days * 86400000);
        },
        
        addHours : function(hours){
            return new DateTime(this.span._millis + hours * 3600000);
        },
        
        addMilliseconds : function(millis){
            return new DateTime(this.span._millis + millis);
        },
        
        addMinutes : function(minutes){
            return new DateTime(this.span._millis + minutes * 60000);
        },
        
        addMonths : function(months){
            var day = this.day();
            var month = this.month() + (months % 12);
            var year = this.year() + Math.round(months / 12);
            
            if(month < 1){
                month = 12 + month;
            }else if(month > 12){
                month -=12;
                year++;
            }
            
            var days = DateTime.daysInMonth(year, month);
            
            if(day > days)
                day = days;
                
            var time = new DateTime(year, month, day);
            return time.add(this.timeOfDay());
        },
        
        addSeconds : function(seconds){
            return new DateTime(this.span._millis + seconds * 1000);
        },
        
        addYears : function(years){
            return this.addMonths(years * 12);
        },
        
        compareTo : function(datetime){
            return this.span.compareTo(datetime.span);
        },
        
        equals : function(datetime){
            return this.span.equals(datetime.span);
        },
        
        subtractDate : function(datetime){
            return new TimeSpan(this.span._millis - datetime.span._millis);
        },
        
        subtractTime : function(timespan){
            return new DateTime(this.span._millis - timespan._millis);
        },
        
        fromSpan : function(what){
            
            var index = 1;
            var daysmonth = DateTime.monthDays;
            var days = this.span.days();
            var num = Math.round(days / 146097);
            days -= num * 146097;
            var num2 = Math.round(days / 36524);
            if(num2 == 4) num2 =3;
            days -= num2 * 36524;
            var num3 = Math.round(days / 1461);
            days -= num3 * 1461;
            var num4 = Math.round(days / 365);
            if(num4 == 4) num = 3;
            if(what == "year")
                return (((((num * 400) + (num2 * 100)) + (num3 * 4)) + num4) + 1);
            days -= num4 * 365;
            if(what != "dayyear"){
                if((num4==3) && ((num2 == 3) || (num3 != 24)))
                    daysmonth = DateTime.monthDaysLeapYear;
                while(days >= daysmonth[index])
                    days -= daysmonth[index++];
                if(what == "month")
                    return index;
            }
            return days + 1;
            
        },
        
        
        format : function (format){
            var shortdays = new Array("", DateTime.strings.Mon, DateTime.strings.Tue, DateTime.strings.Wed, DateTime.strings.Thu, DateTime.strings.Fri, DateTime.strings.Sat, DateTime.strings.Sun);
            var days = new Array("", DateTime.strings.Monday, DateTime.strings.Tuesday, DateTime.strings.Wednesday, DateTime.strings.Thursday, DateTime.strings.Friday, DateTime.strings.Saturday, DateTime.strings.Sunday);
            var shortmonths = new Array("", DateTime.strings.Jan, DateTime.strings.Feb, DateTime.strings.Mar, DateTime.strings.Apr, DateTime.strings.May, DateTime.strings.Jun, DateTime.strings.Jul, DateTime.strings.Aug, DateTime.strings.Sep, DateTime.strings.Oct, DateTime.strings.Nov, DateTime.strings.Dec);
            var months      = new Array("", DateTime.strings.January, DateTime.strings.February, DateTime.strings.March, DateTime.strings.April, DateTime.strings.MayFull, DateTime.strings.June, DateTime.strings.July, DateTime.strings.August, DateTime.strings.September, DateTime.strings.October, DateTime.strings.November, DateTime.strings.December);
            
            var day = this.day();
            var dayOfWeek = this.dayOfWeek();
            var millisecond = this.millisecond();
            var hour = this.hour();
            var minute = this.minute();
            var second = this.second();
            var month = this.month();
            var year = this.year();
            
            var data = new Array();
            
            var yearstr = year + "";
            /*
            if(yearstr.length > 1)
                yearstr = yearstr.substr(0, yearstr.length - 2)
            */
            data["dddd"] = days[dayOfWeek];
            data["ddd"]  = shortdays[dayOfWeek];
            data["dd"] = TimeSpan.pad(day);
            data["d"] = day;
            data["fff"] = millisecond;
            data["ff"] = Math.round(millisecond / 10);
            data["f"] = Math.round(millisecond / 100);
            data["hh"] = TimeSpan.pad(hour > 12 ? hour - 12 : hour);
            data["h"] = hour > 12 ? hour - 12 : hour;
            data["HH"] = TimeSpan.pad(hour);
            data["H"] = hour;
            data["mm"] = TimeSpan.pad(minute);
            data["m"] = minute;
            data["MMMM"] = months[month];
            data["MMM"] = shortmonths[month];
            data["MM"] = TimeSpan.pad(month);
            data["M"] = month;
            data["ss"] = TimeSpan.pad(second);
            data["s"] = second;
            data["tt"] = (hour > 12 ? DateTime.strings.PM : DateTime.strings.AM) ;
            data["t"] = (hour > 12 ? DateTime.strings.P : DateTime.strings.A);
            data["yyyy"] = year;
            data["yyy"] = year;
            data["yy"] = year;
            data["y"] = year;
            data[":"] = DateTime.strings.TimeSeparator;
            data["/"] = DateTime.strings.DateSeparator;
            
            
            var output = "";
            var res = format.split(/(dddd|ddd|dd|d|fff|ff|f|hh|h|HH|H||mm|m|MMMM|MMM|MM|M|ss|s|tt|t|yyyy|yyy|yy|y)?/);
            
            for(var i = 0; i < res.length; i++){
                if(res[i]){
                    if(data[res[i]]){
                        output += data[res[i]];
                    }else{
                        output += res[i];
                    }
                }
            }
            
            return output;
        },
        
        /* Properties */
        date : function(){
            return new DateTime(this.year(), this.month(), this.day());
        },
        
        day : function(){
            return this.fromSpan("day");
        },
        
        dayOfWeek : function(){
            return (this.span.days() + 1) % 7;
        },
        
        dayOfYear : function(){
            return this.fromSpan("dayyear");
        },
        
        hour : function(){
            return this.span.hours();
        },
        
        millisecond : function(){
            return this.span.milliseconds();
        },
        
        minute : function(){
            return this.span.minutes();
        },
        
        month : function(){
            return this.fromSpan("month");
        },
        
        second : function(){
            return this.span.seconds();
        },
        
        timeOfDay : function(){
            return new TimeSpan(this.span._millis % 86400000);
        },
        
        year : function(){
            return this.fromSpan("year");
        }
        
        
        
    }

DateTime.monthDays =         new Array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
DateTime.monthDaysLeapYear = new Array(0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
DateTime.daysInMonth = function (year, month){
    if(DateTime.isLeapYear(year)){
        return DateTime.monthDaysLeapYear[month];
    }else{
        return DateTime.monthDays[month];
    }
};
DateTime.now = function(){
    d = new Date();
    return new DateTime(d.getFullYear(), d.getMonth() + 1, d.getDay(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
};
DateTime.utcNow = function(){
    d = new Date();
    return new DateTime(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDay(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds());
};
DateTime.today = function(){
    var now = DateTime.now();
    return new DateTime(now.year(), now.month(), now.day());
};
DateTime.isLeapYear = function(year){
        if (( (year % 4 == 0) && (year % 100 != 0) ) || (year % 400 == 0))
            return true;
        return false;
};

///*
DateTime.strings = function(){  };
DateTime.strings.Mon 		= "Mon";
DateTime.strings.Monday 	= "Monday";
DateTime.strings.Tue 		= "Tue";
DateTime.strings.Tuesday 	= "Tuesday";
DateTime.strings.Wed 		= "Wed";
DateTime.strings.Wednesday 	= "Wednesday";
DateTime.strings.Thu 		= "Thu";
DateTime.strings.Thursday 	= "Thursday";
DateTime.strings.Fri 		= "Fri";
DateTime.strings.Friday 	= "Friday";
DateTime.strings.Sat 		= "Sat";
DateTime.strings.Saturday 	= "Saturday";
DateTime.strings.Sun 		= "Sun";
DateTime.strings.Sunday 	= "Sunday";
DateTime.strings.Jan 		= "Jan";
DateTime.strings.Januray 	= "January";
DateTime.strings.Feb 		= "Feb";
DateTime.strings.February	= "February";
DateTime.strings.Mar 		= "Mar";
DateTime.strings.March 		= "March";
DateTime.strings.Apr 		= "Apr";
DateTime.strings.April 		= "April";
DateTime.strings.May 		= "May";
DateTime.strings.MayFull 	= "May";
DateTime.strings.Jun 		= "Jun";
DateTime.strings.June		= "June";
DateTime.strings.Jul 		= "Jul";
DateTime.strings.July		= "July";
DateTime.strings.Aug 		= "Aug";
DateTime.strings.August 	= "August";
DateTime.strings.Sep 		= "Sep";
DateTime.strings.September 	= "September";
DateTime.strings.Oct 		= "Oct";
DateTime.strings.October 	= "October";
DateTime.strings.Nov 		= "Nov";
DateTime.strings.November 	= "November";
DateTime.strings.Dec 		= "Dec";
DateTime.strings.December 	= "December";
DateTime.strings.A  		= "A";
DateTime.strings.AM 		= "AM";
DateTime.strings.P			= "P";
DateTime.strings.PM 		= "PM";
DateTime.strings.TimeSeparator = ":";
DateTime.strings.DateSeparator = "/";



/**
* Class that handles time related operations.
* It is very similar to .NET's TimeSpan class
* Find reference and documentation at: http://menendezpoo.com
*/
function TimeSpan(){
    
    var days = 0;
    var hours = 0;
    var minutes = 0;
    var seconds = 0;
    var milliseconds = 0;
    
    switch(arguments.length){
        case 0:
            break;
        case 1:
            milliseconds = arguments[0];
            break;
        case 2:
            days = arguments[0];
            hours = arguments[1];
            break;
        case 3:
            hours = arguments[0];
            minutes = arguments[1];
            seconds = arguments[2];
            break;
        case 4:
            days = arguments[0];
            hours = arguments[1];
            minutes = arguments[2];
            seconds = arguments[3];
            break;
        case 5:
            days = arguments[0];
            hours = arguments[1];
            minutes = arguments[2];
            seconds = arguments[3];
            milliseconds = arguments[4];
            break;
        default:
            throw("No constructor of TimeSpan supports " + arguments.length + " arguments");
    }

    this._millis = (days * 86400 + hours * 3600 + minutes * 60 + seconds) * 1000 + milliseconds;
    
};

TimeSpan.prototype = {
    totalMilliseconds: function () {
        return this._millis;
    },

    /* Methods */
    add : function(timespan){
        return new TimeSpan(timespan._millis + this._millis);
    },
    
    compareTo : function(timespan){
        if(this._millis > timespan._millis) return 1;
        if(this._millis == timespan._millis) return 0;
        if(this._millis < timespan._millis) return -1;
    },
    
    duration : function(){
        return new TimeSpan(Math.abs(this._millis));
    },
    
    equals : function(timespan){
        return this._millis == timespan._millis;
    },
    
    negate : function(){ 
        this._millis *= -1; 
    },
    
    subtract : function(timespan){
        return new TimeSpan(this._millis - timespan._millis);
    },
    
    rounder : function(number){
        if(this._millis < 0)
            return Math.ceil(number);
        return Math.floor(number);
    },
    
    /* Properties */
    
    days : function(){ 
        return this.rounder(this._millis / (24 * 3600 * 1000) ); 
    },
    
    hours : function(){ 
        return this.rounder( (this._millis % (24 * 3600 * 1000)) / (3600 * 1000)); 
    },
    
    milliseconds : function(){ 
        return this.rounder(this._millis % 1000); 
    },
    
    minutes : function(){ 
        return this.rounder( (this._millis % (3600 * 1000)) / (60 * 1000)); 
    },
    
    seconds : function(){ 
        return this.rounder((this._millis % 60000) / 1000); 
    },
    
    totalDays : function(){ 
        return this._millis / (24 * 3600 * 1000); 
    },
    
    totalHours : function(){ 
        return this._millis / (3600 * 1000); 
    },
    
    totalMinutes : function(){ 
        return this._millis / (60 * 1000); 
    },
    
    totalSeconds : function(){ 
        return this._millis / 1000; 
    },
        
    toString : function(){
        return (this._millis < 0 ? "-" : "") + (Math.abs(this.days()) ? TimeSpan.pad(Math.abs(this.days()))  + ".": "") + TimeSpan.pad(Math.abs(this.hours())) + ":" + TimeSpan.pad(Math.abs(this.minutes())) + ":" + TimeSpan.pad(Math.abs(this.seconds())) + "." + Math.abs(this.milliseconds());
    }
};

TimeSpan.pad = function(number){ return (number < 10 ? '0' : '') + number; };